//! \file linux_ptrace_utils.c

#include <link.h>
#include <elf.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>

#include "linux_ptrace_utils.h"

//------------------------------------------------------------------------------

TBGError PtraceOpen(int pid, ptrace_session *pps) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pid >= 0, INVALID_PID);

    pps = (ptrace_session*) malloc(sizeof(ptrace_session));
    CHECK_RET(pps, CANNOT_ALLOC_MEMORY, exit_point, ret);
    
    memset((void*) pps, 0, sizeof(ptrace_session));
    pps->pid = pid;

    CHECK_CALL(PtraceAttach(pps), exit_point, ret);

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceClose(ptrace_session *pps) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);

    if(pps->attached)
        CHECK_CALL(PtraceDetach(pps), free_point, ret);

free_point:
    free(pps);
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceAttach(ptrace_session *pps) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);

    CHECK_RET(!(pps->attached), NOT_DETACHED, exit_point, ret);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
	
    CHECK_RET(ptrace(PTRACE_ATTACH, pps->pid, NULL, NULL) >= 0, CANNOT_ATTACH,
              exit_point, ret); 

	waitpid(pps->pid, NULL, WUNTRACED);
    pps->attached = true;

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceDetach(ptrace_session *pps) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_RET(pps->attached, NOT_ATTACHED, exit_point, ret);

	CHECK_RET(ptrace(PTRACE_DETACH, pps->pid, NULL, NULL) >= 0, CANNOT_DETACH,
              exit_point, ret);
    
    pps->attached = false;

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceReadByteData(ptrace_session *pps, unsigned long addr, void *vptr,
                            unsigned int len) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
    CHECK_DBG_ERR(vptr, NULL_PTR);
    CHECK_RET(addr, INVALID_ADDR, exit_point, ret);
    unsigned int i = 0;
    long temp = 0;
    for(i = 0, errno = 0; i < len; i += sizeof(long)) {
        temp = ptrace(PTRACE_PEEKTEXT, pps->pid, addr + i, NULL);
        CHECK_RET(!errno, CANNOT_PTRACE, exit_point, ret);
        if(len - i >= sizeof(long))
            *((long*) (((char*) vptr) + i)) = temp;
        else 
            for(; i < len; ++i)
                ((char*) vptr)[i] = ((char*) &temp) [i % 4];
    }

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceReadData(ptrace_session *pps, unsigned long addr, void *vptr,
                        unsigned int len_in_addrs) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
    CHECK_DBG_ERR(vptr, NULL_PTR);
    CHECK_RET(addr, INVALID_ADDR, exit_point, ret);

    unsigned int i = 0;
    for(i = 0, errno = 0; i < len_in_addrs; ++i) {
        ((unsigned long*) vptr)[i] =
             (unsigned long) ptrace(PTRACE_PEEKTEXT,
                                    pps->pid, addr + i * sizeof(unsigned long),
                                    NULL);

        CHECK_RET(!errno, CANNOT_PTRACE, exit_point, ret);
    }

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceReadStr(ptrace_session *pps, unsigned long addr,
                       unsigned int len, char *read_str) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
    CHECK_DBG_ERR(read_str, NULL_PTR);
    CHECK_RET(addr, INVALID_ADDR, exit_point, ret);

	CHECK_CALL(PtraceReadByteData(pps, addr, read_str, len), exit_point, ret);

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceWriteData(ptrace_session *pps, unsigned long addr, void *vptr,
                         unsigned int len_in_addrs) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
    CHECK_DBG_ERR(vptr, NULL_PTR);
    CHECK_DBG_ERR(len_in_addrs >= 0, ZERO_LENGTH);
    CHECK_RET(addr, INVALID_ADDR, exit_point, ret);

    unsigned int i = 0;
    for(i = 0, errno = 0; i < len_in_addrs; ++i) {
        ptrace(PTRACE_POKETEXT, pps->pid, addr + i * sizeof(unsigned long), 
               ((unsigned long*) vptr) + i);

        CHECK_RET(!errno, CANNOT_PTRACE, exit_point, ret);
    }

exit_point:
    return ret;
}

//------------------------------------------------------------------------------

TBGError PtraceGetRegs(ptrace_session *pps, struct user_regs *regs) {
    TBGError ret = 0;
    CHECK_DBG_ERR(pps, NULL_PTR);
    CHECK_DBG_ERR(pps->pid >= 0, INVALID_PID);
    CHECK_DBG_ERR(regs, NULL_PTR);

    CHECK_RET(ptrace (PTRACE_GETREGS, pps->pid, NULL, regs) != -1,
              CANNOT_PTRACE, exit_point, ret);
exit_point:
    return ret;
}

